#include "pch.h"
#include "DeferredRenderer.h"
#include "RendererEngine.h"

namespace mini
{
	CDeferredRenderer::CDeferredRenderer(CDevice* pDevice)
		:CRenderer(pDevice)
	{
		mOrder = 235;
		mRenderTypeFlag = RendererType::DeferredRenderer;
		initialise();
	}

	bool CDeferredRenderer::initialise()
	{
		int nWidth = mpDevice->getConfig().screenWidth;
		int nHeight = mpDevice->getConfig().screenHeight;

		CMaterialUnit* pMaterialUnit = new CMaterialUnit;
		pMaterialUnit->setUnitFile(R"(resource\Material\deferred.ma)");
		pMaterialUnit->onInitialise();
		mpMaterialUnit = pMaterialUnit;

		for (int i = 0; i < BUFFER_COUNT; ++i)
		{
			CTexture* pTexture = new CTexture();
			pTexture->initialiseRenderTexture(nWidth, nHeight, DXGI_FORMAT_R32G32B32A32_FLOAT);
			mRenderTextureArray.push_back(pTexture);
			mpRenderTargetViewArray[i] = pTexture->getRenderTargetView();
			pTexture->setSampleState(mpMaterialUnit->getSampler(i));
		}
		int nPass = mpMaterialUnit->getShaderUnitSize();
		if (2 == nPass)
		{
			mDrawFuns[0] = std::bind(&CDeferredRenderer::drawUnit, this, std::placeholders::_1, std::placeholders::_2);
			mDrawFuns[1] = std::bind(&CDeferredRenderer::drawLightEffect, this, std::placeholders::_1, std::placeholders::_2);
		}
		else
		{
			mDrawFuns[0] = nullptr;
			mDrawFuns[1] = nullptr;
		}

		D3D11_TEXTURE2D_DESC depthBufferDesc;
		D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
		// Initialize the description of the depth buffer.
		ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc));

		// Set up the description of the depth buffer.
		depthBufferDesc.Width = nWidth;
		depthBufferDesc.Height = nHeight;
		depthBufferDesc.MipLevels = 1;
		depthBufferDesc.ArraySize = 1;
		depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
		depthBufferDesc.SampleDesc.Count = 1;
		depthBufferDesc.SampleDesc.Quality = 0;
		depthBufferDesc.Usage = D3D11_USAGE_DEFAULT;
		depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
		depthBufferDesc.CPUAccessFlags = 0;
		depthBufferDesc.MiscFlags = 0;

		// Create the texture for the depth buffer using the filled out description.
		mpDevice->getDevice()->CreateTexture2D(&depthBufferDesc, NULL, &mpDepthStencilBuffer);


		// Initailze the depth stencil view description.
		ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc));

		// Set up the depth stencil view description.
		depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
		depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
		depthStencilViewDesc.Texture2D.MipSlice = 0;

		// Create the depth stencil view.
		mpDevice->getDevice()->CreateDepthStencilView(mpDepthStencilBuffer, &depthStencilViewDesc, &mpDepthStencilView);

		mpMeshUnit = GEngine->createBillBoard(nWidth, nHeight);
		
		// Create an orthographic projection matrix for 2D rendering.
		D3DXMatrixOrthoLH(&mOrthoMatrix, (float)nWidth, (float)nHeight, 1, 1000);
		auto pShaderUnit = mpMaterialUnit->getShaderUnit(1);
		pShaderUnit->addStructedBuffer("g_Light", sizeof(LightColor), sizeof(LightColor) * 255, 3, 0);
		mLights.resize(500);
		mLightsAngle.resize(mLights.size());
	//	srand(GetTickCount());
		for( int i = 0; i < mLights.size(); ++i )
		{
			mLightsAngle[i] = rand() % 360;
			float pt = sin(mLightsAngle[i]) * 400;
			mLights[i].position = CPoint3F(300, -400, 300);
			mLights[i].mDiffuseColor = CPoint4F( 0.4f, 0, 0.0f, 1);
		}
		pShaderUnit->updateStructedBuffer(3, &mLights[0]);
		return true;
	}

	CDeferredRenderer::~CDeferredRenderer()
	{
		SafeDelete(mpMaterialUnit);
		for (auto& item : mRenderTextureArray)
		{
			SafeDelete(item);
		}
		mRenderTextureArray.clear();
		SafeRelease(mpDepthStencilBuffer);
		SafeRelease(mpDepthStencilView);
	}

	void CDeferredRenderer::addUnit(CUnit* pUnit, CMaterialUnit* pMaterialUnit, EntityType Type, INT32 zLayer)
	{
		if (!pMaterialUnit->isShadow())
		{
			CRenderer::addUnit(pUnit, pMaterialUnit, Type, zLayer);
		}
	}

	void CDeferredRenderer::commitToGpu()
	{
		for( int i = 0;i < 2; ++i)
		{
			auto pShaderUnit = mpMaterialUnit->getShaderUnit(i);
			mDrawFuns[i](mpDeferredContext, pShaderUnit);
		}

		auto pShaderUnit = mpMaterialUnit->getShaderUnit(2);
		if (nullptr != pShaderUnit)
		{
			for (auto& item : mAlphaRenderUnits)
			{
				for (auto& child : (item.second))
				{
					pShaderUnit->apply(mpDeferredContext);
					child->render(mpDeferredContext);
				}
			}
		}
	}

	void CDeferredRenderer::drawUnit(DeviceContext* pContext, CShaderUnit* pShaderUnit)
	{
		// Bind the render target view array and depth stencil buffer to the output render pipeline.
		pContext->OMSetRenderTargets(BUFFER_COUNT, mpRenderTargetViewArray, mpDepthStencilView);

		// Clear the render target buffers.
		for (auto item : mRenderTextureArray)
		{
			item->clearRenderTargetView(CPoint4F(0,0,0,1));
		}

		// Clear the depth buffer.
		pContext->ClearDepthStencilView(mpDepthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);
		pShaderUnit->setShader(pContext);
		auto pCamera = CRendererEngine::getSinglePtr()->getCamera();
		if (nullptr != pCamera)
		{
			pShaderUnit->setViewMatrix(&pCamera->getViewMatrix());
			pShaderUnit->setProjectMatrix(&pCamera->getProjectMatrix());
		}

		for (auto& item : mZbufferOffRenderUnits)
		{
			auto _item = (item.second);
			pShaderUnit->apply(mpDeferredContext);
			(_item.second)->render(mpDeferredContext);
		}

		for (auto& item : mRenderUnits)
		{
			for (auto& child : (item.second))
			{
				pShaderUnit->setWorldMatrix(&child->getMatrix());
				pShaderUnit->apply(mpDeferredContext);
				child->render(mpDeferredContext);
			}
		}
		mpDevice->recoverBackBuffer(pContext);
	}

	void CDeferredRenderer::drawLightEffect(DeviceContext* pContext, CShaderUnit* pShaderUnit)
	{
		pShaderUnit->setShader(pContext);
		for (int i = 0; i < mLights.size(); ++i)
		{
			mLights[i].position = CPoint3F(mLights[i].position.x, mLights[i].position.y - 5.1f, mLights[i].position.z);
		}
		pShaderUnit->updateStructedBuffer(3, &mLights[0]);
		for (auto item : mLightActors)
		{
			auto pCamera = CRendererEngine::getSinglePtr()->getCamera();
			if (nullptr != pCamera)
			{
				pShaderUnit->setWorldMatrix(&mpMeshUnit->getMatrix());
				pShaderUnit->setViewMatrix(&pCamera->getViewMatrix());
				pShaderUnit->setOrthoMatrix(&mOrthoMatrix);
				pShaderUnit->updatePSConstBuffer("LightBuffer", &item->getDirection());

				mRenderTextureArray[G_COLOR]->commitToGpu(pContext, 0);
				mRenderTextureArray[G_NORMAL]->commitToGpu(pContext, 1);
				mRenderTextureArray[G_POSITION]->commitToGpu(pContext, 2);
				pShaderUnit->apply(mpDeferredContext);
				mpMeshUnit->render(pContext);
			}
		}
	}

}
